﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Web.Script.Serialization;

namespace KsWare.Breconium.Core {

	public partial class Json {

		private static readonly JavaScriptSerializer sDynamicJsonSerializer;
		internal static readonly JavaScriptSerializer sJavaScriptSerializer;

		static Json() {
			sDynamicJsonSerializer = new JavaScriptSerializer();
			sDynamicJsonSerializer.RegisterConverters(new[] {new DynamicJsonConverter()});
			sJavaScriptSerializer=new JavaScriptSerializer();
		}

		public static dynamic ToDynamic(string json) {
			dynamic obj = sDynamicJsonSerializer.Deserialize(json, typeof(object));	
			return obj;
		}

		public static string ToString(object obj) {
			var json=sJavaScriptSerializer.Serialize(obj);
			return json;
		}

		public static Json ToJsonObject(string json) { 
			return new Json(json,true);
		}
	}

	public enum JsonObjectType {
		None,
		Null,
		Primitive,
		Array,
		Dictionary,
		Complex,
	}
	
	public partial class Json:IEnumerable {
		private string mJson;
		private object mJsObj;

		#region Contructors
		public Json(JsonObjectType type) {
			mJson = null;
			switch (type) {
				case JsonObjectType.None      : mJsObj=null; break;
				case JsonObjectType.Primitive : mJsObj=null; break;
				case JsonObjectType.Array     : mJsObj=new System.Collections.ArrayList();break;
				case JsonObjectType.Dictionary: mJsObj=new Dictionary<string,object>();break;
			}
		}
		public Json(string json, bool parse) {
			if(parse) {
				mJson = json;
				mJsObj =sJavaScriptSerializer.Deserialize<object>(json);
			} else {
				mJsObj = json;
			}
		}
		public Json(Dictionary<string,object> dic) {
			mJson = null;
			mJsObj = dic;
		}
		public Json(object[] arr) {
			mJson = null;
			mJsObj = arr;
		}
		public Json(object obj) {
			if(obj is Json){
				mJsObj = ((Json)obj).mJsObj;
				mJson = ((Json)obj).mJson;
			}else{
				mJson = null;
				mJsObj = obj;
			}
		}
		#endregion

		public Json this[string path]{
			get { return new Json(GetValue(path,true)); }
			set {
				if(path=="") SetNativeValue(value);
				else throw new NotImplementedException();
			}
		}
		
		public string String(string path) {
			var s=GetValue(path,true);
			if(s==null) return null;
			return s.ToString();
		}

		public long? LongZ(string path) {
			var s=GetValue(path,true);
			if(s==null) return null;
			var l=long.Parse(s.ToString());
			return l;
		}

		public long Long(string path, long defaultValue) {
			var s=GetValue(path,true);
			if(s==null) return defaultValue;
			var l=long.Parse(s.ToString());
			return l;
		}

		public bool? Bool(string path) {
			var s=GetValue(path,true);
			if(s==null) return null;
			var l=bool.Parse(s.ToString());
			return l;
		}

		public Json Path(string path) {
			var obj=GetValue(path,true);
			return new Json(obj);
		}

		public bool IsNull {get {return mJsObj==null;}}
		public bool IsArray {get {return IsArrayInternal(mJsObj);}}
		public bool IsDictionary {get { return IsDictionaryInternal(mJsObj); }}
		public bool IsPrimitive {get { return IsPrimitiveInternal(mJsObj); }}

		public string[] Keys {
			get {
				if(IsArray) {
					var count = ((object[]) mJsObj).Length;
					var keys = new string[count];
					for (int i = 0; i < count; i++) keys[i] = i.ToString();
					return keys;
				} else if(IsDictionary) {
					var keys=new string[((Dictionary<string, object>) mJsObj).Count];
					((Dictionary<string, object>) mJsObj).Keys.CopyTo(keys,0);
					return keys;
				} else {
					return new []{""};
				}
			}
		}

		public object[] Values {
			get {
				if(mJsObj is ArrayList) {
					var count = ((ArrayList) mJsObj).Count;
					var values = new object[count];
					((ArrayList) mJsObj).CopyTo(values,0);
					return values;
				} else if(mJsObj is object[]) {
					var count = ((object[]) mJsObj).Length;
					var values = new object[count];
					((object[]) mJsObj).CopyTo(values,0);
					return values;
				} else if(IsDictionary) {
					var count = ((Dictionary<string, object>) mJsObj).Count;
					var values = new object[count];
					((Dictionary<string, object>) mJsObj).Values.CopyTo(values,0);
					return values;
				} else {
					if(mJsObj==null)return new object[0];
					return new object[]{mJsObj};
				}
			}
		}

		public Dictionary<string,object> ToDictionary() {
			var dic = new Dictionary<string, object>();
			var keys = Keys;
			var vals = Values;
			for (int i = 0; i < keys.Length; i++) {
				dic.Add(keys[i],vals[i]);
			}
			return dic;
		} 

		public object NativeValue{get { return mJsObj; } set { SetNativeValue(value); }}

		private void SetNativeValue(object value) { mJsObj = value; }

		public object GetValue(string path, bool noException) {
			var pathLeft = path;
			var pathCur = "";
			var o = mJsObj;
			var dic = o as Dictionary<string, object>;
			var arr = o as object[];
			if(path!="") {
				while (true) {
					var p=pathLeft.Split(new[]{'.'},2);
					var propName = p[0]; 
					pathLeft = p.Length == 2 ? p[1] : null;
					pathCur += (pathCur.Length>0?".":"")+propName;
					if(dic!=null) {
						if(!dic.ContainsKey(propName)) {
							if(noException) {
								Trace.WriteLine("WARNING JsonObject object null reference. path='"+pathCur+"'..'"+pathLeft+"'");
								return null;
							} else {
								throw new NullReferenceException("Object null at "+pathCur);
							}
						}
						o = dic[propName];
						dic = o as Dictionary<string, object>;
						arr = o as object[];
					} else if(arr!=null) {
						int i;
						if(!int.TryParse(propName, out i)) {
							if(noException) {
								Trace.WriteLine("WARNING JsonObject Object is not an array! "+pathCur);
								return null;
							} else {
								throw new InvalidCastException("Object is not an array! "+pathCur);
							}
						}
						o = arr[i];
						dic = o as Dictionary<string, object>;
						arr = o as object[];
					} else {
						return null;
					}
					if(pathLeft==null) break;
				}				
			}

			return o;
		}

		public IEnumerator GetEnumerator() { 
			if(mJsObj is object[]) return ((object[]) mJsObj).GetEnumerator();
			if(mJsObj is ArrayList) return ((ArrayList) mJsObj).GetEnumerator();
			if(mJsObj is Dictionary<string,object>) return ((Dictionary<string,object>) mJsObj).GetEnumerator();
			return new object[0].GetEnumerator();
		}

		public override string ToString() {
			if(mJson!=null) return mJson;
			//return "{Json not serialized}";
			return sJavaScriptSerializer.Serialize(mJsObj);
		}

		private static bool IsArrayInternal(object obj) {return (obj is object[]) || (obj is ArrayList);}
		private static bool IsDictionaryInternal(object obj) {return obj is Dictionary<string,object>;}
		private static bool IsPrimitiveInternal(object obj) { return !(obj is object[]) && !(obj is ArrayList) && !(obj is Dictionary<string,object>); }

	}

	public static  class JsonExtension {

		public static string ToString(this object obj) { return Json.ToString(obj); }
	} 

	public static class JsonTest {

		public static void TestArray() {
			var json=Json.ToJsonObject("[1,2,3,\"d\"]");
			var val=json.Long("1", 0);
		}

		public static void TestArray2() {
			var json=Json.ToJsonObject("{\"Array\":[1,2,3,\"d\"]}");
			var val=json.Long("Array.1", 0);
		}
	}
}
